package memorphic.slide
{
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	
	import memorphic.core.IHistory;
	
	import mx.managers.IHistoryManagerClient;
	
	use namespace slide_internal;
	
	
	// TODO: Complete the integration with Flex history management
	[Event(name="stateChanged", type="StateEvent")]
	[Event(name="beforeStateChanged", type="StateEvent")]
	public class StateManager extends EventDispatcher implements IHistory,IHistoryManagerClient
	{
		
		
		private var __stateURI:String;
		private var aliasList:Object;
		private var nonHistoryList:Object;
		
		private var __homeStateURI:String;
		
		private static var instanceCount:uint = 0;
		private var myInstanceCount:uint;
		
		slide_internal var addAllStatesToHistory:Boolean = false;
		slide_internal static var instances:Dictionary = new Dictionary(true);
		
		
		public static var historyLength:int = 100;
		
		private var __stateHistory:Array = new Array();
		private var __historyPosition:int = -1;
		
		
		[Bindable("stateChanged")]
		public function get stateHistory():Array
		{
			return __stateHistory.concat();
		}
		
		
		/**
		 * 
		 */
		[Bindable("stateChanged")]
		public function get stateURI():String
		{
			return __stateURI;
		}
		public function set stateURI(uri:String):void
		{
			setStateURI(uri, false);
		}
		
		
		/**
		 * 
		 */
		protected function setStateURI(uri:String, noHistoryAppend:Boolean=false):void
		{
			if(aliasList[uri]){
				uri = aliasList[uri];
			}
			var oldState:String = __stateURI;
			__stateURI = uri;
			if(!StateMemento(saveState()).equals(currentHistoryItem)){
				dispatchEvent(new StateEvent(StateEvent.BEFORE_STATE_CHANGE, uri, oldState));
				if(!noHistoryAppend){
					appendStateToLocalHistory();
				}
				dispatchEvent(new StateEvent(StateEvent.STATE_CHANGE, uri, oldState));
			}
		}
		
		
		
		/**
		 * 
		 */
		[Bindable("stateChanged")]
		public function get isAtRoot():Boolean
		{
			return stateURI.split("/").length == 1;
		}
		
		
		/**
		 * 
		 */
		[Bindable("stateChanged")]
		public function get isHome():Boolean
		{
			return stateURI == homeStateURI;
		}
		
		
	
		/**
		 * 
		 */
		public function get homeStateURI():String
		{
			return __homeStateURI;
			
		}
		
		
		/**
		 * Returns true if the current state is a parent state of the state given
		 */
		public function currentStateIsParentOf(otherState:String, inclusive:Boolean=false):Boolean
		{
			return stateIsParentOf(stateURI, otherState, inclusive);
		}
		
		
		/**
		 * Returns true if the current state is a child state of the state given
		 */
		public function currentStateIsChildOf(otherState:String, inclusive:Boolean=false):Boolean
		{
			return stateIsParentOf(otherState, stateURI, inclusive);
		}
		
		
		
		/**
		 * Returns true if the state given as the "parent" argument is a parent state of "child".
		 */
		public function stateIsParentOf(parent:String, child:String, inclusive:Boolean=false):Boolean
		{
			if(inclusive && parent == child){
				return true;
			}
			if(parent.length >= child.length){
				return false;
			}
			var parentParts:Array = parent.split("/");
			var childParts:Array = child.split("/");
			var parentLen:int = parentParts.length;
			if(childParts.length <= parentLen){
				return false;
			}
			for(var i:int=0; i<parentLen; i++){
				if(childParts[i] != parentParts[i]){
					return false;
				}
			}
			return true;
		}
		
		
		/**
		 * 
		 * @param homeState If omitted, will be the same as initialState
		 */
		public function StateManager(initialState:String="", homeState:String=null)
		{
			instances[this] = 1;
			myInstanceCount = instanceCount++;
			
			aliasList = new Object();
			nonHistoryList = new Object();
			if(homeState == null){
				homeState = initialState;
			}
			__homeStateURI = homeState;
			stateURI = initialState;
		}
		
		
		
		/**
		 * Defines an alias for a state uri. It's a bit like a URL redirect
		 * 
		 * For example, if you use:
		 * 
		 * 	addStateAlias("app/content/home", "home");
		 * 
		 * then setting stateURI to "home" will actually set it to "app/content/home".
		 */ 
		public function addStateAlias(primary:String, alias:String):void
		{
			aliasList[alias] = primary;
		}
		
		
		
		/**
		 * Prevents a state from being added to the state history
		 * 
		 */
		public function addNonHistoryState(uri:String):void
		{
			nonHistoryList[uri] = true;
			// it's possible that the app has already been in this state, so remove it from the history now.
			var n:int = __stateHistory.length;
			while(n--){
				if(StateMemento(__stateHistory[n]).viewStateURI == uri){
					__stateHistory.splice(n, 1);
					if(n <= __historyPosition){
						__historyPosition--;
					}
				}
			}
		}
		
		
		/**
		 * Set the application state to be up one level.
		 * 
		 * For example, if the current state is "a/b/c", the new state would be "a/b"
		 */ 
		public function goUp():void
		{
			var uri:String = stateURI;
			var upURI:String = uri.substr(0, uri.lastIndexOf("/"));
			stateURI = upURI;
		}
		
		
		public function goHome():void
		{
			stateURI = __homeStateURI;
		}
		

/////////////////////////////////////////////////////////////////
//
//		IHistory interface
//	
		[Bindable("stateChanged")]
		public function get canGoForward():Boolean
		{
			return __historyPosition +1 < __stateHistory.length;
		}
		
		[Bindable("stateChanged")]
		public function get canGoBack():Boolean
		{
			return __historyPosition > -1;
		}
		
		public function back():void
		{
			go(-1);
		}
		
		public function forward():void
		{
			go(1);
		}
		
		public function go(offset:int):void
		{
			if(offset == 0) return;
			var newPosition:int = __historyPosition + offset;
			if(newPosition < __stateHistory.length && newPosition > -1){
				// setStateURI(StateHistoryItem(__stateHistory[__historyPosition]).stateURI, true);
				loadState(__stateHistory[newPosition]);
				__historyPosition = newPosition;
			}
			
		}
		
		[Bindable("stateChanged")]
		public function get historyPosition():uint
		{
			return __historyPosition;
		}
		
		[Bindable("stateChanged")]
		public function get historyLength():uint
		{
			return __stateHistory.length;
		}
		
		// not in the interface, but related
		public function get currentHistoryItem():StateMemento
		{
			if(historyPosition < 0){
				return null;
			}
			return StateMemento(__stateHistory[historyPosition]);
		}
		
/////////////////////////////////////////////////////////////////
//
//		IHistoryManagerClient interface
//
		/**
		 * Override createHistoryItem(), saveState() and loadState() in subclasses, to add better History-management 
		 * support that is more specific to your application
		 */
		public function saveState():Object
		{
			return createHistoryItem();
		}
		
		/**
		 * Override createHistoryItem(), saveState() and loadState() in subclasses, to add better History-management 
		 * support that is more specific to your application
		 */
		public function loadState(state:Object):void
		{
			// stateURI = (state as StateHistoryItem).stateURI;
			setStateURI((state as StateMemento).viewStateURI, true);
		}
		
		
		public override function toString():String
		{
			return getQualifiedClassName(this) + myInstanceCount;
		}
		
		
		protected function appendStateToLocalHistory():void
		{
			if(!addAllStatesToHistory && nonHistoryList[stateURI]){
				return;
			}
			
			if(__stateHistory.length > __historyPosition+1){
				__stateHistory.splice(__historyPosition+1, __stateHistory.length-__historyPosition);
			}
			__stateHistory.push(createHistoryItem());
			if(__stateHistory.length > historyLength){
				__stateHistory.shift();
			}
			__historyPosition = __stateHistory.length - 1;
		}
		
		
		protected function createHistoryItem():StateMemento
		{
			return new StateMemento(stateURI);
		}
		
	}
	
	
	
}